package de.sordul.md5crack.impl.optimizedMultiCore;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import de.sordul.md5crack.IFragmentBuilder;

public class CrackWorker implements Runnable {

	private MessageDigest 		messageDigest;
	private StringBuffer 		md5String;
	private byte[] 				inputByte, md5Byte;
	private String 				candidate, candidateMD5, alphaNumerics;
	private StringBuilder 		toHexStringBuilder;
	
	private String md5; 
	private int length;
	private boolean running;
	private IFragmentBuilder fragmentBuilder;
	private MCOptimizedMD5Crack crack;
	private BigInteger computedHashes = BigInteger.valueOf(0l);
	
	public CrackWorker(WorkerSettings settings, IFragmentBuilder fragmentBuilder) {
		md5String = new StringBuffer();
		toHexStringBuilder = new StringBuilder(32);
		alphaNumerics = "0123456789abcdef";
		this.md5 = settings.getMd5();
		this.length = settings.getLength();
		this.crack = settings.getCrack();
		this.running = true;
		this.fragmentBuilder = fragmentBuilder;
		
		try {
			messageDigest = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}		
	}

	@Override
	public void run() {
		do {
			candidate = fragmentBuilder.getNextString();
			
			if(candidate == null) {
				running = false;
				break;
			}
			
			if(candidate.length() > length) {
				candidate = null;
				break;
			}
			candidateMD5 = computeMD5(candidate);
			computedHashes = computedHashes.add(BigInteger.valueOf(1l));

		} while(!candidateMD5.equals(md5) && running);

		if(running) {
			synchronized(crack.getWaitObj()) {
				crack.setCandidate(candidate);
				crack.getWaitObj().notifyAll();
			}			
		}
	}

	/**
	 * Computes the MD5 of a given string.
	 * @param inputString A string
	 * @return The corresponding MD5
	 */
	private String computeMD5(String inputString) {	
		inputByte = inputString.getBytes();
		md5String.delete(0, md5String.length());
		
		// Build MD5 byte[] representation
		messageDigest.reset();
		messageDigest.update(inputByte);
		md5Byte = messageDigest.digest();
		
		return toHex(md5Byte);
	}
	
	/**
	 * Converts the given byte array to a string.
	 * Original code taken from: <a href="http://stackoverflow.com/a/3889053">http://stackoverflow.com/a/3889053</a>
	 * @param digest The byte[] to convert
	 * @return A string
	 */
	private String toHex(byte[] digest) {
		toHexStringBuilder.delete(0, toHexStringBuilder.length());
		
	    for (byte b : digest) {
	        int bi = b & 0xff;
	        toHexStringBuilder.append(alphaNumerics.charAt(bi >> 4));
	        toHexStringBuilder.append(alphaNumerics.charAt(bi & 0xf));
	    }
	    return toHexStringBuilder.toString();
	}

	public void stop() {
		this.running = false;
	}

	public BigInteger getComputedHashes() {
		return computedHashes;
	}
}
